/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.core;

import java.util.LinkedHashSet;
import java.util.NavigableSet;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.apache.log4j.Logger;

import be.destin.skos.html.SkosHtmlManager;

@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlType(propOrder={"searchURL","previewURL","completeURL","harvestURL","harvestCondition", "harvestFailure","harvestCount","atomURL"})
public class SchemeUsage {
	
	/** log4j category */
	private static Logger log = Logger.getLogger(SchemeUsage.class);

	private NoScheme inScheme = null; // ConceptScheme or NotationScheme
	
	private String application = null;
	private String role = null;
	private long total = 0; // count is an int!
	private int diskMirrorAddressSize = 0; // No disk Mirroring by default

	/*
	 * 3roundstones.com/vocab/vocab.pl: skos:prefLabel for role? label:plural and label:inverseSingular = qualified altLabel?
	 */
	private String searchURL;	// Brings the user to a search result based on current concept, application and role
	private String previewURL;	// Creates a preview (Internet Explorer Accelerators) on a search result based on current concept, application and role
    private String completeURL; // If the search result size is one and this URL is defined, this brings the user directly to display of an item
    private String harvestURL;	// Harvest count, main category, "purity" but also (eventually) counts in a time serie for a search based on current concept, application and role
    private String harvestCondition;	// [field] or [field@param] that must be present to have harvesting
    private String harvestFailure;	// Regular expression on the result to test for a failure
    private String harvestCount;	// Regular expression on the result to test for a success and extract the count OR XSLT to extract count, category, time serie?
	private String atomURL;	// Get a search result based on current concept, application and role and formatted as an Atom feed
    private boolean narrowerIncluded = false;
    private TreeMap<Integer,LinkedHashSet<String>> freqMap = null; // created by getFreqMap...
    
    private Pattern harvestFailurePattern = null;
    private Pattern harvestCountPattern = null;

	public SchemeUsage() {
		super();
	}  
 
	public SchemeUsage(NoScheme aScheme, String anApplication, String aRole) {
		super();
		this.setInScheme(aScheme);
		this.application = anApplication;
		this.role = aRole;
	}  
 
	public SchemeUsage(NoScheme aScheme, String anApplication, String aRole, boolean narrowerIncluded) {
		super();
		this.setInScheme(aScheme);
		this.application = anApplication;
		this.role = aRole;
		this.narrowerIncluded = narrowerIncluded;
	}  
 
	public SchemeUsage(NoScheme aScheme, SchemeUsage model) {
		super();
		this.setInScheme(aScheme);
		// this.setCount(model.getCount());
		// this.role = model.role;
		this.application = model.getApplication();
		this.role = model.getRole();
		this.total = model.getTotal();
		this.narrowerIncluded = model.narrowerIncluded;
		this.setSearchURL(model.getSearchURL());
		this.setPreviewURL(model.getPreviewURL());
		this.setCompleteURL(model.getCompleteURL());
		this.setHarvestURL(model.getHarvestURL());
		this.setHarvestCondition(model.getHarvestCondition());
		this.setHarvestFailure(model.getHarvestFailure());
		this.setHarvestCount(model.getHarvestCount());
		this.setAtomURL(model.getAtomURL());
	}  
 
	public String getHarvestCondition() {
		return harvestCondition;
	}

	public void setHarvestCondition(String harvestCondition) {
		this.harvestCondition = harvestCondition;
	}

	/**
	 * @return the completeURL
	 */
	public String getCompleteURL() {
		return completeURL;
	}
	/**
	 * @return the previewURL
	 */
	public String getPreviewURL() {
		return previewURL;
	}

	@XmlAttribute(required=true)
	public String getApplication() {
		return this.application;
	}

	public void setApplication(String key) {
		if (key == null) this.application=null;
		else this.application = key.trim();
	}

	/**
	 * @return the searchURL
	 */
	public String getSearchURL() {
		return searchURL;
	}


	/**
	 * @param completeURL the completeURL to set
	 */
	public void setCompleteURL(String completeURL) {
		this.completeURL = completeURL;
	}


	/**
	 * @param previewURL the previewURL to set
	 */
	public void setPreviewURL(String previewURL) {
		this.previewURL = previewURL;
	}


	/**
	 * @param searchURL the searchURL to set
	 */
	public void setSearchURL(String searchURL) {
		this.searchURL = searchURL;
	}

	/**
	 * @return the narrowerIncluded
	 */
	@XmlAttribute(required = false)
	public boolean isPropagated() {
		return narrowerIncluded;
	}


	/**
	 * @param narrowerIncluded the narrowerIncluded to set
	 */
	public void setPropagated(boolean narrowerIncluded) {
		this.narrowerIncluded = narrowerIncluded;
	}


	/**
	 * @return the freqMap
	 */
	@XmlTransient
	public TreeMap<Integer,LinkedHashSet<String>> getFreqMap() {
		if (this.freqMap == null) {
			this.freqMap = new TreeMap<Integer,LinkedHashSet<String>>();
		}
		return this.freqMap;
	}


	/**
	 * @param freqMap the freqMap to set
	 */
	public void putFreqMap(int frequency, Concept aConcept) {
		if (aConcept == null) return;
		if (frequency == 0) return;
		String anAbout = aConcept.getAbout();
		LinkedHashSet<String> currConcepts = this.getFreqMap().get(frequency);
		if (currConcepts == null) {
			currConcepts = new LinkedHashSet<String>();
			currConcepts.add(anAbout);
			this.freqMap.put(frequency, currConcepts);
		} else {
			if (!currConcepts.contains(anAbout)) {
				currConcepts.add(anAbout);
			}
		}
	}
	
	public String freqMapToHtml(SkosHtmlManager skosHtmlManager, int limit) {
		NavigableSet<Integer> freqs = this.getFreqMap().descendingKeySet();
		StringBuffer result = new StringBuffer();
		for (Integer aFreq : freqs) {
			result.append("<li>");
			result.append(aFreq.toString());
			result.append(':');
			if (limit <= 0) {
				result.append(" ...</li>");
				break;
			}
			boolean first = true;
			for (String anAbout: this.getFreqMap().get(aFreq)) {
				limit--;
				if (!first) {
					//result.append(',');
					result.append("<br/>");
				} else {
					first = false;
				}
				result.append(' ');
				Concept aConcept = this.inScheme.getConcept(anAbout);
				if (aConcept != null) {
					result.append(aConcept.toHtmlLabelUsage(skosHtmlManager));
				} else {
					result.append(this.inScheme.getAbout());
					result.append(SchemeInterface.separator);
					result.append(anAbout);
				}
			}
			result.append("</li>");
		}
		return result.toString();	
	}


	/**
	 * @return the inScheme
	 */
	@XmlTransient
	public NoScheme getInScheme() {
		return this.inScheme;
	}


	/**
	 * @param inScheme the inScheme to set
	 */
	public void setInScheme(NoScheme inScheme) {
		if (inScheme == null) {
			log.error("null Scheme for a SchemeUsage?");
		} else if (inScheme != null && inScheme instanceof ConceptScheme || inScheme instanceof NotationScheme) {
			this.inScheme = inScheme;
		} else {
			log.error(inScheme.getAbout()+" is not a ConceptScheme or a NotationScheme.");
		}
	}
	
	public String toString() {
		return "Usage of Scheme:"+inScheme.getAbout()+", application:"+application+", role:"+role+"="+total+(narrowerIncluded?"<":"");
	}

	public String key() {
		return inScheme.getAbout()+":"+application+SchemeInterface.separator+role;
	}

	public String getHarvestURL() {
		return harvestURL;
	}

	public void setHarvestURL(String harvestURL) {
		this.harvestURL = harvestURL;
	}

	public String getHarvestFailure() {
		return harvestFailure;
	}

	public void setHarvestFailure(String harvestFailure) {
		this.harvestFailure = harvestFailure;
		this.harvestFailurePattern = null;
		if (this.harvestFailure != null && !this.harvestFailure.isEmpty()) { 
			try {
				this.harvestFailurePattern = Pattern.compile(this.harvestFailure);
			} catch (PatternSyntaxException e) {
				String message = this.inScheme.getAbout()+", harvestFailure regular expression \""+this.harvestFailure+"\" is invalid: " +e.toString();
				log.error(message);
				this.inScheme.getErrors().add(message);
			}
		}
	}

	public boolean harvestFailed(String data) {
		if (harvestFailurePattern == null) return false; // No pattern, no failure!
		return harvestFailurePattern.matcher(data).find();
	}

	public String getHarvestCount() {
		return harvestCount;
	}

	public void setHarvestCount(String harvestCount) {
		this.harvestCount = harvestCount;
		this.harvestCountPattern = null;
		if (this.harvestCount != null && !this.harvestCount.isEmpty()) {
			try {
				this.harvestCountPattern = Pattern.compile(this.harvestCount);
			} catch (PatternSyntaxException e) {
				String message = this.inScheme.getAbout()+", harvestCount regular expression \""+this.harvestCount+"\" is invalid: " +e.toString();
				log.error(message);
				this.inScheme.getErrors().add(message);
			}
		}
	}

	public Count harvestCount(Concept aConcept, String data) {
		if (harvestCountPattern == null) {
			log.error(harvestCount+": not a counting pattern ready to analyse "+data);
			return null; // No pattern, no count!
		}
		String matched = null;
		try {
			Matcher match = harvestCountPattern.matcher(data);
			if (match.find()) {
				if (match.groupCount() >= 1) {
					matched = match.group(1); // the searched count is the group 1
				} else { // No group, we must count matches
					int countMatch = 1;
					while (match.find()) countMatch++;
					Count aCount = new Count();
					aCount.setRole(this.role);
					aCount.setCount(countMatch);
					return aCount;
				}
			} else {
				log.info('"'+this.harvestCount+"\" not found in \""+data+"\"");
			}
		} catch (Exception e) {
			return null;
		}
		if (matched==null || matched.isEmpty()) return null;
		try {
			matched = matched.replaceAll("\\D+", "");
			if (matched.isEmpty()) return null;
			Integer number = Integer.parseInt(matched);
			Count aCount = new Count();
			aCount.setRole(this.role);
			aCount.setCount(number);
			return aCount;
		} catch (NumberFormatException e) {
			String message = aConcept.getScheme_About()+", harvested count is not an Integer: \""+matched+"\", " +e.toString();
			aConcept.addError(message);
			return null;
		}
	}

	public String getAtomURL() {
		return atomURL;
	}

	public void setAtomURL(String atomURL) {
		this.atomURL = atomURL;
	}

	/**
	 * @return the role
	 */
	@XmlAttribute(required=true)
	public String getRole() {
		return role;
	}

	/**
	 * @param role the role to set
	 */
	public void setRole(String role) {
		this.role = role;
	}

	/**
	 * @return the total
	 */
	@XmlTransient
	public long getTotal() {
		return total;
	}

	/**
	 * @param total the total to set
	 */
	public void setTotal(long total) {
		this.total = total;
	}

	/**
	 * @return the diskMirrorAddressSize
	 */
	@XmlAttribute(required=false)
	public int getDiskMirrorAddressSize() {
		return diskMirrorAddressSize;
	}

	/**
	 * @param diskMirrorAddressSize the diskMirrorAddressSize to set
	 */
	public void setDiskMirrorAddressSize(int diskMirrorAddressSize) {
		this.diskMirrorAddressSize = diskMirrorAddressSize;
	}
}
